Introducción a ML con Python 🐍

00_01 - Python 101

Iván Moreno

Python 101

¿Qué es Python?

  • Lenguaje de programación interpretado (no compilado)
  • Multiplataforma
  • Multiparadigma (imperativo, orientado a objetos, funcional)
  • Tipado dinámico (no es necesario declarar el tipo de las variables)
  • Orientado a objetos
  • Extensible (módulos y paquetes) y portable (e.g., Jython, una implementación de Python que corre sobre la JVM)

¿Por qué Python?

  • Sintaxis sencilla y legible (indentación)
  • Gran cantidad de librerías (compatible con C y C++)
  • Comunidad activa
  • Multiplataforma

Instalación

Anaconda

  • Distribución de Python (y R) para computación científica
  • Gestor de paquetes (conda)
  • Gestor de entornos (conda)
  • Dos versiones: Anaconda (GUI, ~3GB) y Miniconda (CLI, ~400MB)

Jupyter

  • Cuaderno de notas interactivo para la computación científica
  • Celdas de código, texto (Markdown) e imágenes / gráficos
  • Kernels (procesos encargados de ejecutar el código en un lenguaje determinado)
  • Interfaz web (Jupyter Notebook)

Descarga Anaconda

https://www.anaconda.com/products/distribution#Downloads

Inicialización del entorno

  • Windows: Anaconda Prompt
  • Linux & Mac: Terminal
conda create -n intro python=3.11
conda activate intro
python --version
python -i -c "print('sss 🐍')" # Ctrl + D o exit() para salir

Gestión de entornos con conda

  • Crear un entorno: conda create -n <nombre> python=<versión>
  • Activar un entorno: conda activate <nombre>
  • Desactivar un entorno: conda deactivate
  • Eliminar un entorno: conda env remove -n <nombre> --all
  • Listar entornos: conda env list

Gestión de paquetes con conda

  • Instalar un paquete: conda install <paquete>
  • Eliminar un paquete: conda remove <paquete>
  • Listar paquetes: conda list

pip vs conda

  • pip es el gestor de paquetes oficial de Python
  • conda es el gestor de paquetes de Anaconda
  • conda es más completo (y lento) que pip (e.g., gestión de entornos)
  • conda se basa en binarios, mientras que pip en código fuente (portabilidad, velocidad)
  • conda comprueba potenciales conflictos entre dependencias, con resolución automática de conflictos
  • pip usa PyPI (Python Package Index), mientras que conda usa Anaconda Cloud (pese a que también puede usar PyPI y otros repositorios)

Sintaxis

Comentarios

# Esto es un comentario de una línea

"""
Esto es un comentario
de varias líneas
"""

Variables

# Asignación
a = 1
b = 2
c = a + b

# Tipado dinámico
a = 1
a = "hola"

Operadores

Operador Descripción
+ Suma
- Resta
* Multiplicación
** Potencia
/ División
// División entera
% Módulo
== Igualdad (valor)
is Identidad (referencia)
!= Desigualdad (valor)
is not No identidad (referencia)
> / >= / < / <= Mayor / Mayor o igual / Menor / Menor o igual
and Logical AND
or Logical OR
not Logical NOT
in Contenido
not in No contenido

Condicionales

if a > b:
    print("a es mayor que b")
elif a < b:
    print("a es menor que b")
else:
    print("a es igual que b")

Bucles

# Bucle while
i = 0
while i < 10:
    print(i)
    i += 1

# Bucle for
for i in range(10):
    print(i)
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

Funciones

def funcion(a, b):
    return a + b


print(funcion(1, 2))
3

Clases

  • Las clases son plantillas para crear objetos
  • Los objetos son instancias de una clase
  • Las clases pueden tener atributos y métodos
  • Los atributos son variables de la clase
  • Los métodos son funciones de la clase

Clases – métodos

  • Los métodos pueden ser de instancia, de clase o estáticos
  • Cuándo utilizar cada tipo de método:
    • De instancia: acciones pertinenentes a una instancia (self)
    • De clase: crear constructores alternativos (factory), acceder a los atributos de la clase (cls) (e.g., tipo de figura geométrica)
    • Estático: utilidades de la clase (e.g., conversión de unidades)

Clases – Ejemplo

  • __init__: constructor
  • self: referencia a la instancia de la clase
  • @classmethod: decorador para métodos de clase
  • @staticmethod: decorador para métodos estáticos
class Clase:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def metodo(self):
        print(self.a + self.b)

    @classmethod
    def metodo_clase(cls):
        print("Método de clase")

    @staticmethod
    def metodo_estatico():
        print("Método estático")


objeto = Clase(1, 2)
objeto.metodo()
Clase.metodo_clase()
Clase.metodo_estatico()
3
Método de clase
Método estático

Clases – Constructores alternativos

class Clase:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    @classmethod
    def from_string(cls, string):
        a, b = string.split(",")
        return cls(a, b)


objeto = Clase.from_string("1,2")
print(objeto.a)
1

Clases – Herencia

  • La herencia permite crear clases que heredan los atributos y métodos de otra clase
  • La clase padre se conoce como superclase y la clase hija como subclase
  • La subclase puede sobreescribir los métodos de la superclase, pero no existe el concepto de sobrecarga de métodos
class ClaseHija(Clase):
    def __init__(self, a, b, c):
        super().__init__(a, b)
        self.c = c

    def metodo(self):
        super().metodo()
        print(self.c)
        
objeto = ClaseHija(1, 2, 3)
objeto.metodo()

Tipos de datos

Números

# Números
a = 1 # Entero
b = 1.0 # Float
c = 1 + 1j # Complejo

Cadenas de texto

# Cadenas de texto
a = "Hola"
b = 'Hola'
c = """Hola
Mundo"""

Cadenas de texto – F-strings

# F-strings
a = "mundo"
b = f"Hola {a}"
print(b)
Hola mundo

Booleanos

# Booleanos
a = True
b = False

None

  • Valor especial que representa la ausencia de valor
  • Equivalente a null en otros lenguajes
# None
a = None

Tipos de Secuencias

  • Tuplas
  • Listas
  • Secuencias (range, enumerate, zip)

Tuplas

  • Colecciones ordenadas de elementos
  • No se pueden modificar
  • Se pueden iterar (múltiples veces)
# Tuplas
tupla = (1, 2, 3, 4, 5)
tupla[0]
tupla[0:2]
tupla[0] = 0 # Error

Listas

  • Colecciones ordenadas de elementos
  • Se pueden modificar
  • Se pueden iterar (múltiples veces)
  • Se pueden rebanar (slicing), concatenar, ordenar y filtrar
# Listas
lista = [1, 2, 3, 4, 5]
lista[0]
lista[0:2]
lista[0] = 0

Secuencias

  • Objetos iterables immutables (no se pueden modificar)
# Secuencias (range)
secuencia = range(10)
secuencia[0]
secuencia[0:2]
# Secuencias (enumerate)
secuencia = enumerate(["a", "b", "c"])
for i, v in secuencia:
    print(i, v)
0 a
1 b
2 c
# Secuencias (zip)
secuencia = zip(["a", "b", "c"], [1, 2, 3])
for i, v in secuencia:
    print(i, v)
a 1
b 2
c 3

Tipos de Mappings

  • Diccionarios
  • Conjuntos

Diccionarios

  • Colecciones de pares clave-valor
# Diccionarios
diccionario = {"a": 1, "b": 2, "c": 3}
diccionario["a"]
diccionario["a"] = 0

Conjuntos (sets)

  • Colecciones no ordenadas de elementos únicos
# Conjuntos
conjunto = {1, 2, 3, 4, 5}
conjunto.add(6)
conjunto.remove(6)

Comprehensions

  • Sintaxis para crear colecciones a partir de otras colecciones
# Comprensión de listas
lista = [x + 1 for x in range(10)]
print(lista)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Comprensión de diccionarios
diccionario = {x: x + 1 for x in range(10)}
print(diccionario)
{0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}
# Comprensión de conjuntos
conjunto = {x + 1 for x in range(10)}
print(conjunto)
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

Conceptos avanzados

Decoradores

  • Funciones que modifican el comportamiento de otras funciones
  • Se utilizan para añadir funcionalidad a una función sin modificarla
def decorador(funcion):
    def wrapper(*args, **kwargs):
        print("Antes de la función")
        funcion(*args, **kwargs)
        print("Después de la función")

    return wrapper


@decorador
def funcion():
    print("Función")


funcion()
Antes de la función
Función
Después de la función

Expresiones lambda

  • Funciones anónimas (sin nombre)
  • Se utilizan para crear funciones sencillas que no se van a reutilizar
  • Se pueden utilizar en cualquier lugar donde se necesite una función
  • Sintaxis: lambda <argumentos>: <expresión>
  • Equivalente a def funcion(<argumentos>): return <expresión>
funcion = lambda x: x + 1
print(funcion(1))
2

Generadores

  • Funciones que devuelven un iterador
  • Se utilizan para crear secuencias de elementos
  • Iterables una única vez, no se almacenan en memoria
  • Sintaxis: yield <elemento>
  • Equivalente a return <elemento> pero no finaliza la ejecución de la función
def generador():
    for i in range(10):
        yield i


for i in generador():
    print(i)
0
1
2
3
4
5
6
7
8
9

Expresiones generadoras

  • Sintaxis para crear generadores de forma más sencilla
  • Se utilizan para crear secuencias de elementos
lista = [1, 2, 3, 4, 5]
generador = (x + 1 for x in lista)
for i in generador:
    print(i)
2
3
4
5
6

Estructura de programas

Módulos

  • Un módulo es un fichero con extensión .py
  • Un módulo puede contener funciones, clases, variables, etc.
  • Un módulo puede importar otros módulos
  • Un módulo puede ser ejecutado directamente o importado por otro módulo

Módulos – Ejemplo

  • __name__: nombre del módulo
  • __main__: nombre del módulo principal
  • Por qué utilizar if __name__ == "__main__"
    • Organización del código (separar código de ejecución directa del código de importación)
    • Evitar ejecución de código al importar un módulo
    • Encapsulación de código de ejecución directa
# modulo.py
def funcion():
    print("Hola mundo")

if __name__ == "__main__":
    funcion()
$ python modulo.py
Hola mundo
# otro_modulo.py
import modulo

modulo.funcion()
$ python otro_modulo.py
Hola mundo

Paquetes

  • Un paquete es un conjunto de módulos
  • Un paquete es un directorio con un fichero __init__.py
  • Un paquete puede contener otros paquetes (subpaquetes)
  • Un paquete puede ser importado por otro paquete o módulo

__init__.py

  • Fichero vacío que indica que un directorio es un paquete
  • Se ejecuta al importar un paquete
  • Se utiliza para inicializar el paquete (e.g., importar módulos)

Paquetes – Ejemplo

$ tree
.
├── main.py
└── paquete
    ├── __init__.py
    └── modulo.py
# main.py
import paquete.modulo

paquete.modulo.funcion()
$ python main.py
Hola mundo

Entornos virtuales

Entornos virtuales

  • Un entorno virtual es un directorio que contiene una instalación de Python
  • Permite aislar las dependencias de un proyecto
  • Permite tener múltiples versiones de Python instaladas en el sistema
  • Permite tener múltiples versiones de las dependencias de un proyecto

Entornos virtuales – Ejemplo

  • Crear un entorno virtual: python -m venv venv
  • Activar un entorno virtual: source venv/bin/activate (Linux & Mac) o venv\Scripts\activate.bat (Windows)
  • Desactivar un entorno virtual: deactivate (Linux & Mac) o venv\Scripts\deactivate.bat (Windows)
  • Instalar dependencias: pip install <paquete>
  • Exportar dependencias: pip freeze > requirements.txt
$ python -m venv venv
$ source venv/bin/activate # o venv\Scripts\activate.bat
$ pip install numpy
$ pip freeze > requirements.txt
$ deactivate # o source venv/bin/deactivate, venv\Scripts\deactivate.bat